/**
 * \file dmxcon/stage.c
 * \author Christian Spoo
 * \date 01.08.2010
 *
 * DMXcon stage management
 */

#include <errno.h>
#include <malloc.h>
#include <string.h>

#include <dmxcon/stage.h>

struct stage_t *stage_create(void) {
  struct stage_t *stage = (struct stage_t *)malloc(sizeof(struct stage_t));
  if (stage) {
    memset(stage, 0, sizeof(struct stage_t));
  }

  return stage;
}

int stage_destroy(struct stage_t *stage) {
  if (stage) {
    struct devlist_entry_t *dp = stage->devices;
    struct fixturelist_entry_t *fp = stage->fixtures;

    /* Freeing device list */
    while (dp) {
      struct devlist_entry_t *p = dp->next;
      device_destroy(dp->device);
      free(dp);
      dp = p;
    }

    /* Freeing fixture list */
    while (fp) {
      struct fixturelist_entry_t *p = fp->next;
      fixture_destroy(fp->fixture);
      free(fp);
      fp = p;
    }

    free(stage);
    return 0;
  }

  errno = EINVAL;
  return -1;
}

int stage_register_device(struct stage_t *stage, struct device_t *device, unsigned int start_addr, unsigned int end_addr) {
  if (stage) {
    struct devlist_entry_t *entry = (struct devlist_entry_t *)malloc(sizeof(struct devlist_entry_t));
    if (!entry) {
      errno = ENOMEM;
      return -1;
    }

    memset(entry, 0, sizeof(struct devlist_entry_t));

    if (stage->devices) {
      struct devlist_entry_t *p = stage->devices;
      
      while (p->next)
        p = p->next;

      p->next = entry;
    }
    else
      stage->devices = entry;

    /* Fill list entry */
    entry->start_addr = start_addr;
    entry->end_addr = end_addr;
    entry->device = device;

    /* Set device range */
    device_set_range(device, end_addr - start_addr);

    return 0;
  }

  errno = EINVAL;
  return -1;
}

int stage_unregister_device(struct stage_t *stage, struct device_t *device) {
  if (stage) {
    if (stage->devices) {
      struct devlist_entry_t *p, *prev;

      prev = NULL;

      for (p = stage->devices; p != NULL; prev = p, p = p->next) {
        if (p->device == device) {
          if (prev == NULL)
            stage->devices = p->next;
          else
            prev->next = p->next;

          free(p);
          return 0;
        }
      }

      errno = ENOENT;
      return -1;
    }

    errno = EINVAL;
    return -1;
  }

  errno = EINVAL;
  return -1;
}

int stage_set_channel(struct stage_t *stage, unsigned int addr, unsigned char value) {
  if (stage) {
    struct devlist_entry_t *p = stage->devices;

    while (p) {
      if ((p->start_addr <= addr) && (p->end_addr >= addr))
        return device_set_channel(p->device, (addr - p->start_addr), value);

      p = p->next;
    }

    errno = ENOENT;
    return -1;
  }

  errno = EINVAL;
  return -1;
}

int stage_set_channels(struct stage_t *stage, unsigned int start_addr, unsigned char *data, unsigned int len) {
  if (stage) {
    struct devlist_entry_t *p = stage->devices;
    unsigned int addr = start_addr;

    /* Find device with first address */
    while (p) {
      if ((p->start_addr <= addr) && (p->end_addr >= addr))
        break;

      p = p->next;
    }

    /* Check if found */
    if (!p) {
      errno = ENOENT;
      return -1;
    }

    do {
      /* Set channels in current device */
      do {
        if (device_set_channel(p->device, (addr - p->start_addr), data[addr - start_addr]) < 0)
          return -1;

        addr++;
      } while ((p->end_addr >= addr) && (addr < start_addr+len));

      /* Do we have finished? */
      if (addr == start_addr+len)
        return 0;

      /* No, advance to the next device */
      while (p) {
        if ((p->start_addr <= addr) && (p->end_addr >= addr))
          break;

        p = p->next;
      }
    } while (p);

    /* We have left the device list, because we missed some addresses */
    errno = ENOENT;
    return -1;
  }

  errno = EINVAL;
  return -1;
}

unsigned char stage_get_channel(struct stage_t *stage, unsigned int addr) {
  if (stage) {

  }

  errno = EINVAL;
  return -1;
}

int stage_get_channels(struct stage_t *stage, unsigned int start_addr, unsigned char *data, unsigned int len) {
  if (stage) {

  }

  errno = EINVAL;
  return -1;
}

int stage_refresh_devices(struct stage_t *stage) {
  if (stage) {
    struct devlist_entry_t *p = stage->devices;

    while (p) {
      device_refresh(p->device);
    }
    return 0;
  }

  errno = EINVAL;
  return -1;
}

int stage_register_fixture(struct stage_t *stage, struct fixture_t *fixture) {
  if (stage) {
    if (fixture) {
      struct fixturelist_entry_t *entry = (struct fixturelist_entry_t *)malloc(sizeof(struct fixturelist_entry_t));

      /*! \todo sanity checks: fixture address valid and does not overlap device boundaries */

      if (entry) {
        struct fixturelist_entry_t *p = stage->fixtures;

        if (p) {
          while (p->next)
            p = p->next;

          p->next = entry;
        }
        else
          stage->fixtures = entry;
    
        memset(entry, 0, sizeof(struct fixturelist_entry_t));
        entry->fixture = fixture;
        fixture->stage = stage;

        return 0;
      }

      return -1;
    }
  }
  
  errno = EINVAL;
  return -1;
}

int stage_unregister_fixture(struct stage_t *stage, struct fixture_t *fixture) {
  if (stage) {
    if (fixture) {
      struct fixturelist_entry_t *p = stage->fixtures;
      struct fixturelist_entry_t *prev = NULL;

      while (p) {
        if (p->fixture == fixture)
          break;

        prev = p;
        p = p->next;
      }

      if (!p) {
        errno = ENOENT;
        return -1;
      }

      if (prev)
        /* Relink previous entry */
        prev->next = p->next;
      else
        /* Set new list head */
        stage->fixtures = p->next;

      free(p);
      fixture->stage = NULL;
      return 0;
    } 
  }

  errno = EINVAL;
  return -1;
}

